import 'dart:io';
import 'dart:math';

import 'package:analyzer/dart/ast/ast.dart';
import 'package:dart_bean/cass_info.dart';
import 'package:dart_bean/feature/json_field.dart';
import 'package:dart_bean/generator.dart';
import 'package:dart_bean/setting.dart';
import 'package:dart_bean/util/dart_source_updater.dart';
import 'package:dart_bean/util/ext.dart';

///生成 fromToJson，加入DartBean必要依赖，加入生成的 part
class AutoJsonBean {
  final DartSourceUpdater updater;
  final Iterable<CassInfo> singleFileClassList;

  late final file = singleFileClassList.first.file;
  late final partFile = getPartFile(Generator.libDir, Generator.genDir, file);

  AutoJsonBean(this.updater, this.singleFileClassList);

  void update() {
    _autoBean();
    _autoImport();
  }

  String? _autoConstructor(CassInfo c, String classContent) {
    if (isStaticFromJson) return null;
    if (!hasDefaultConstructor(c)) {
      final replacement = '  ${c.name}();';
      return _addMethod(classContent, replacement);
    }
    return null;
  }

  /// a Constructor without parameters, maybe has not empty body.
  bool hasDefaultConstructor(CassInfo c) {
    return c.self.members.any((e) => e is ConstructorDeclaration && e.parameters.parameters.isEmpty && e.name == null);
  }

  ///a Constructor without parameters and empty body.
  bool hasEmptyConstructor(CassInfo c) {
    final constructor = '${c.name}();';
    return c.self.members.any((e) => e is ConstructorDeclaration && e.toString() == constructor);
  }

  String? _autoFromJson(CassInfo c, String classContent) {
    final old = classContent;
    final staticFromJson = getStaticFromJson(c);
    if (isStaticFromJson) {
      if (hasDefaultFactoryFromJson(c)) {
        if (staticFromJson == null) {
          classContent = _addMethod(classContent, getStaticFromJsonCode(c));
        }
        classContent = deleteDefaultFactoryFromJson(c, classContent);
        if (hasEmptyConstructor(c)) classContent = deleteEmptyConstructor(c, classContent);
      } else {
        //FromJsonConstructor name can't be used for static method also
        final fromJsonConstructor = getFromJsonConstructor(c);
        if (fromJsonConstructor == null && staticFromJson == null) {
          classContent = _addMethod(classContent, getStaticFromJsonCode(c));
        } else {
          final count = countStaticFromJson(c);
          if (count > 1) {
            classContent = classContent.replaceAll(staticFromJson.toString(), '');
            classContent = _addMethod(classContent, getStaticFromJsonCode(c));
          }
        }
      }
    } else {
      if (isDefaultStaticFromJson(c, staticFromJson)) {
        classContent = classContent.replaceFirst(staticFromJson.toString(), '');
      }
      final constructor = getFromJsonConstructor(c);
      if (constructor == null) {
        classContent = _addMethod(classContent, getFactoryFromJsonCode(c));
      }
    }

    final oldName = '\$${c.name}FromJson(json)';
    final newName = getFromJsonMethodName(c);
    if (!classContent.contains(newName) && classContent.contains(oldName)) {
      classContent = classContent.replaceFirst(oldName, newName);
    }
    return classContent != old ? classContent : null;
  }

  String getStaticFromJsonCode(CassInfo c) {
    return 'static ${c.name} fromJson(dynamic json) => ${getFromJsonMethodName(c)}(json);';
  }

  String getFactoryFromJsonCode(CassInfo c) {
    return 'factory ${c.name}.fromJson(dynamic json) => ${getFromJsonMethodName(c)}(json);';
  }

  bool isDefaultStaticFromJson(CassInfo c, MethodDeclaration? m) {
    return m.toString() == getStaticFromJsonCode(c);
  }

  MethodDeclaration? getStaticFromJson(CassInfo c) {
    return c.self.members.firstOrNull((e) => e is MethodDeclaration && e.name.toString() == 'fromJson') as MethodDeclaration?;
  }

  int countStaticFromJson(CassInfo c) {
    final list = c.self.members.where((e) => e is MethodDeclaration && e.name.toString() == 'fromJson');
    return list.length;
  }

  String deleteStaticFromJson(CassInfo c, String classContent, [String replacement = '']) {
    final defaultFromJsonConstructor = getFromJsonConstructor(c)!.toString();
    return classContent.replaceFirst(defaultFromJsonConstructor, replacement);
  }

  String deleteDefaultFactoryFromJson(CassInfo c, String classContent, [String replacement = '']) {
    final defaultFromJsonConstructor = getFromJsonConstructor(c)!.toString();
    return classContent.replaceFirst(defaultFromJsonConstructor, replacement);
  }

  String deleteEmptyConstructor(CassInfo c, String classContent) {
    return classContent.replaceFirst('${c.name}();', '');
  }

  ConstructorDeclaration? getFromJsonConstructor(CassInfo c) {
    return c.self.members.firstOrNull((e) => e is ConstructorDeclaration && e.name.toString() == 'fromJson') as ConstructorDeclaration?;
  }

  bool hasDefaultFactoryFromJson(CassInfo c) {
    return getFromJsonConstructor(c).toString() == getFactoryFromJsonCode(c);
  }

  String? _autoToJson(CassInfo c, String classContent) {
    final name = c.name;
    final exist = c.self.members.any((e) => e is MethodDeclaration && e.name.toString() == 'toJson');
    if (!exist) {
      final replacement = '  Map<String, dynamic> toJson() => ${getToJsonMethodName(c)}(this);';
      return _addMethod(classContent, replacement);
    } else {
      final oldName = '\$${name}ToJson(this)';
      final newName = getToJsonMethodName(c);
      if (!classContent.contains(newName) && classContent.contains(oldName)) {
        return classContent.replaceFirst(oldName, newName);
      }
      return null;
    }
  }

  void _autoBean() {
    for (var c in singleFileClassList) {
      updater.updateClassContent(c, _autoConstructor);
      updater.updateClassContent(c, _autoFromJson);
      updater.updateClassContent(c, _autoToJson);
    }
  }

  void _autoImport() {
    updater.updateImport(_autoImportJsonConvert);
    updater.updateImport(_autoImportJsonAnnotation);
    updater.updateImport(_clearOldImport);
    updater.updateImport(_importPart);
    updater.updateImport(_clearNotExistedFileImport);
  }

  String? _autoImportJsonConvert(String fileContent) {
    // import 'package:dart_bean/gen/json/base/json_convert.dart';
    final file = getBaseJsonFile(Generator.genDir, jsonCastFileName);
    final dartFile = singleFileClassList.first.file;
    final target = genImportPath(file, dartFile);
    if (!_hasImport(target)) {
      final import = "import '$target';";
      return addImport(fileContent, import, target);
    }
    return null;
  }

  String? _autoImportJsonAnnotation(String fileContent) {
    // import 'package:dart_bean/gen/base/json_field.dart';
    final file = getBaseJsonFile(Generator.genDir, jsonSupportFileName);
    final dartFile = singleFileClassList.first.file;
    final target = genImportPath(file, dartFile);

    //import 'package:json_annotation/json_annotation.dart';
    if (_hasImport('package:json_annotation/json_annotation.dart')) {
      if (_hasUsingJsonField()) {
        if (!_hasImport(target)) {
          // import 'package:dart_bean/gen/base/json_field.dart' show JSONField;
          final import = "import '$target' show ${JsonFieldFeature.annotationClassName};";
          return addImport(fileContent, import, target);
        }
      } else {
        return null;
      }
    }

    if (!_hasImport(target)) {
      final import = "import '$target';";
      return addImport(fileContent, import, target);
    }
    return null;
  }

  String _addMethod(String classContent, String method) {
    final start = classContent.lastIndexOf('}');
    return classContent.replaceRange(start, start + 1, '\n$method\n}');
  }

  String addImport(String fileContent, String import, String uri) {
    final c = singleFileClassList.first;
    if (c.imports.isNotEmpty) {
      final imports = "\r\n$import\r\nimport ";
      return fileContent.replaceFirst(RegExp('[\r\n]*import[\\s\t]+'), imports).trimLeft();
    } else if (c.parts.isNotEmpty) {
      final imports = "\r\n$import\r\npart ";
      return fileContent.replaceFirst(RegExp('[\r\n]*part[\\s\t]+'), imports).trimLeft();
    } else {
      c.imports.add(SimpleImportDirective(uri));
      final atReg = RegExp(r'[\r\n]*@').firstMatch(fileContent)!;
      final classReg = RegExp(r'[\r\n]*class[\t\s]+').firstMatch(fileContent);
      final enumReg = RegExp(r'[\r\n]*enum[\t\s]+').firstMatch(fileContent);
      var index = min(classReg?.start ?? fileContent.length, enumReg?.start ?? fileContent.length);
      index = min(atReg.start, index);
      if (index == 0) {
        return '$import\r\n$fileContent';
      }
      final head = fileContent.substring(0, index);
      final tail = fileContent.substring(index);
      return '$head\r\n$import$tail';
    }
  }

  bool _hasImport(String import) {
    return singleFileClassList.first.imports.any((e) => e.uri == import);
  }

  bool _hasUsingJsonField() {
    return singleFileClassList.any((e) => JsonFieldFeature.hasClassUsingJsonField(e));
  }

  String? _clearOldImport(String fileContent) {
    // import 'generated/json/base/xx.dart';
    // import '../generated/json/base/json_field.dart';
    // import 'package:xxx/generated/json/xx.g.dart';
    final jsonField = RegExp(r"[\r\n]*import[\t\s]+'[\w:/.]+/generated/json/[\w/.]+\.dart';");
    if (fileContent.contains(jsonField)) {
      return fileContent.replaceAll(jsonField, '');
    }
    return null;
  }

  String? _importPart(String fileContent) {
    //part 'package:json_bean/gen/json/user.g.dart';
    final target = genImportPath(partFile, file);
    final partLine = RegExp("[\r\n]+part[\\s\t]+'$target';");
    if (!fileContent.contains(partLine)) {
      final p = "part '$target';";
      final importReg = RegExp(r'[\r\n]*import[\s\t]+[^;]+;');
      final allMatches = importReg.allMatches(fileContent);
      final m = allMatches.last;
      var ip = '${m.group(0)!}\n\n$p';
      return fileContent.replaceAll(m.group(0)!, ip);
    }
    return null;
  }

  String? _clearNotExistedFileImport(String fileContent) {
    final imports = singleFileClassList.first.imports;
    if (imports.isEmpty) return null;
    var newContent = fileContent;
    for (var import in imports) {
      final uri = import.uri;
      final reg = RegExp("package:${Generator.projectName}/(?<path>.+)", dotAll: true);
      final m = reg.firstMatch(uri);
      final path = m?.namedGroup('path');
      if (path != null) {
        final f = File('${Generator.libDir.path}$separator$path'.fixePathStyle());
        if (!f.existsSync()) {
          newContent = newContent.replaceAll(RegExp("[\r\n]*import 'package:${Generator.projectName}/$path[^;]+;"), '');
        }
      }
    }
    return newContent != fileContent ? newContent : null;
  }
}
